<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>百聆(BaiLing) - 智能语音对话</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <style>
        :root {
            --primary: #4a6cf7;
            --primary-dark: #3b59d6;
            --accent: #ff6b6b;
            --success: #1dd1a1;
            --warning: #feca57;
            --bg-dark: #0f172a;
            --bg-light: #1e293b;
            --text-light: #f1f5f9;
            --text-muted: #94a3b8;
            --glass-bg: rgba(30, 41, 59, 0.7);
            --glass-border: rgba(255, 255, 255, 0.1);
            --transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
        }

        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;
        }

        body {
            background: linear-gradient(135deg, #0d0f1e 0%, #1a1e3c 100%);
            color: var(--text-light);
            min-height: 100vh;
            overflow-x: hidden;
            position: relative;
        }

        /* 科技感背景 */
        .tech-bg {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            z-index: 0;
            overflow: hidden;
            background: radial-gradient(circle at center, rgba(74, 108, 247, 0.05) 0%, transparent 80%);
        }

        .tech-bg::before {
            content: "";
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background:
                radial-gradient(circle at 20% 30%, rgba(74, 108, 247, 0.1) 0%, transparent 20%),
                radial-gradient(circle at 80% 70%, rgba(255, 107, 107, 0.1) 0%, transparent 25%),
                radial-gradient(circle at 40% 80%, rgba(31, 221, 161, 0.1) 0%, transparent 15%);
            z-index: 1;
        }

        .grid-line {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-image:
                linear-gradient(rgba(74, 108, 247, 0.05) 1px, transparent 1px),
                linear-gradient(90deg, rgba(74, 108, 247, 0.05) 1px, transparent 1px);
            background-size: 40px 40px;
            z-index: 0;
        }

        /* 顶部标题栏 */
        header {
            background: rgba(10, 15, 30, 0.8);
            backdrop-filter: blur(10px);
            padding: 18px 30px;
            text-align: center;
            border-bottom: 1px solid rgba(74, 108, 247, 0.3);
            position: relative;
            z-index: 10;
            box-shadow: 0 5px 20px rgba(0, 0, 0, 0.5);
        }

        header h1 {
            font-size: 2.5rem;
            font-weight: 800;
            margin: 0;
            background: linear-gradient(90deg, #4a6cf7, #8a6cf7, #ff6b6b);
            -webkit-background-clip: text;
            background-clip: text;
            -webkit-text-fill-color: transparent;
            letter-spacing: 1px;
            position: relative;
            display: inline-block;
            text-shadow: 0 0 10px rgba(74, 108, 247, 0.3);
        }

        .subtitle {
            font-size: 1rem;
            color: var(--text-muted);
            margin-top: 8px;
            letter-spacing: 2px;
            text-transform: uppercase;
        }

        /* 控制面板 */
        .control-panel {
            max-width: 1200px;
            margin: 20px auto;
            padding: 25px;
            background: rgba(10, 15, 30, 0.6);
            backdrop-filter: blur(10px);
            border-radius: 20px;
            border: 1px solid rgba(74, 108, 247, 0.2);
            position: relative;
            z-index: 5;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
        }

        .status-box {
            display: flex;
            align-items: center;
            padding: 15px 20px;
            background: rgba(15, 23, 42, 0.5);
            border-radius: 15px;
            margin-bottom: 20px;
            border: 1px solid rgba(74, 108, 247, 0.2);
        }

        .status-indicator {
            width: 12px;
            height: 12px;
            border-radius: 50%;
            margin-right: 15px;
            box-shadow: 0 0 10px currentColor;
        }

        .status-text {
            font-size: 1.1rem;
            font-weight: 600;
            letter-spacing: 0.5px;
        }

        .status-indicator.connecting {
            background: var(--warning);
            animation: pulse 1.5s infinite;
        }

        .status-indicator.connected {
            background: var(--success);
        }

        .status-indicator.disconnected {
            background: var(--accent);
        }

        .status-indicator.recording {
            background: var(--primary);
            animation: pulse 1s infinite;
        }

        .control-buttons {
            display: flex;
            gap: 15px;
            flex-wrap: wrap;
        }

        .control-btn {
            flex: 1;
            min-width: 150px;
            padding: 18px 0;
            border-radius: 15px;
            background: linear-gradient(135deg, rgba(74, 108, 247, 0.3), rgba(59, 89, 214, 0.2));
            border: 1px solid rgba(74, 108, 247, 0.5);
            color: var(--text-light);
            font-size: 1.1rem;
            font-weight: 600;
            cursor: pointer;
            transition: var(--transition);
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 10px;
            box-shadow: 0 5px 15px rgba(74, 108, 247, 0.2);
        }

        .control-btn:hover {
            background: linear-gradient(135deg, rgba(74, 108, 247, 0.5), rgba(59, 89, 214, 0.4));
            transform: translateY(-3px);
            box-shadow: 0 8px 20px rgba(74, 108, 247, 0.4);
        }

        .control-btn:disabled {
            opacity: 0.5;
            cursor: not-allowed;
            transform: none;
            box-shadow: none;
        }

        .control-btn.stop {
            background: linear-gradient(135deg, rgba(255, 107, 107, 0.3), rgba(200, 80, 80, 0.2));
            border: 1px solid rgba(255, 107, 107, 0.5);
        }

        .control-btn.stop:hover {
            background: linear-gradient(135deg, rgba(255, 107, 107, 0.5), rgba(200, 80, 80, 0.4));
            box-shadow: 0 8px 20px rgba(255, 107, 107, 0.4);
        }

        .control-btn.interrupt {
            background: linear-gradient(135deg, rgba(254, 202, 87, 0.3), rgba(220, 170, 60, 0.2));
            border: 1px solid rgba(254, 202, 87, 0.5);
        }

        .control-btn.interrupt:hover {
            background: linear-gradient(135deg, rgba(254, 202, 87, 0.5), rgba(220, 170, 60, 0.4));
            box-shadow: 0 8px 20px rgba(254, 202, 87, 0.4);
        }

        /* 对话容器 */
        #dialogue-container {
            max-width: 1200px;
            margin: 20px auto;
            padding: 25px;
            height: calc(100vh - 420px);
            min-height: 300px;
            position: relative;
            z-index: 5;
            overflow-y: auto;
            display: flex;
            flex-direction: column;
            gap: 20px;
            background: rgba(10, 15, 30, 0.6);
            backdrop-filter: blur(10px);
            border-radius: 20px;
            border: 1px solid rgba(74, 108, 247, 0.2);
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
        }

        /* 自定义滚动条 */
        #dialogue-container::-webkit-scrollbar {
            width: 8px;
        }

        #dialogue-container::-webkit-scrollbar-track {
            background: transparent;
        }

        #dialogue-container::-webkit-scrollbar-thumb {
            background: var(--primary);
            border-radius: 4px;
        }

        /* 消息样式 */
        .message {
            padding: 20px 25px;
            border-radius: 20px;
            max-width: 85%;
            position: relative;
            opacity: 0;
            transform: translateY(30px);
            animation: fadeUp 0.5s forwards;
            transition: var(--transition);
            backdrop-filter: blur(10px);
            border: 1px solid var(--glass-border);
            box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3);
        }

        .message:hover {
            transform: translateY(-5px);
            box-shadow: 0 12px 30px rgba(0, 0, 0, 0.4);
        }

        .message.role-user {
            background: linear-gradient(45deg, rgba(74, 108, 247, 0.25), rgba(59, 89, 214, 0.15));
            align-self: flex-end;
            border-top-right-radius: 5px;
            animation-delay: 0.1s;
        }

        .message.role-bot {
            background: linear-gradient(45deg, rgba(30, 41, 59, 0.7), rgba(15, 23, 42, 0.5));
            align-self: flex-start;
            border-top-left-radius: 5px;
            animation-delay: 0.2s;
        }

        .message.role-admin {
            background: linear-gradient(45deg, rgba(31, 221, 161, 0.25), rgba(15, 23, 42, 0.2));
            align-self: flex-start;
            animation-delay: 0.3s;
        }

        .message.role-system {
            background: linear-gradient(45deg, rgba(254, 202, 87, 0.25), rgba(15, 23, 42, 0.2));
            align-self: center;
            text-align: center;
            max-width: 70%;
            animation-delay: 0.4s;
        }

        .message-content {
            font-size: 1.1rem;
            line-height: 1.6;
            position: relative;
            z-index: 2;
        }

        .role-tag {
            position: absolute;
            top: -12px;
            font-size: 0.8rem;
            font-weight: 700;
            padding: 5px 15px;
            border-radius: 20px;
            text-transform: uppercase;
            letter-spacing: 1px;
            box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
        }

        .role-user .role-tag {
            background: var(--primary);
            right: 20px;
            box-shadow: 0 0 10px rgba(74, 108, 247, 0.5);
        }

        .role-bot .role-tag {
            background: var(--accent);
            left: 20px;
            box-shadow: 0 0 10px rgba(255, 107, 107, 0.5);
        }

        .role-admin .role-tag {
            background: var(--success);
            left: 20px;
            box-shadow: 0 0 10px rgba(31, 221, 161, 0.5);
        }

        .role-system .role-tag {
            background: var(--warning);
            left: 50%;
            transform: translateX(-50%);
            color: #333;
            box-shadow: 0 0 10px rgba(254, 202, 87, 0.5);
        }

        .timestamp {
            display: block;
            font-size: 0.75rem;
            color: var(--text-muted);
            margin-top: 8px;
            text-align: right;
        }

        .loading {
            text-align: center;
            padding: 40px;
            font-size: 1.2rem;
            color: var(--text-muted);
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            z-index: 10;
            background: var(--glass-bg);
            border-radius: 20px;
            padding: 30px 50px;
            backdrop-filter: blur(10px);
            border: 1px solid var(--glass-border);
            box-shadow: 0 15px 35px rgba(0, 0, 0, 0.4);
        }

        .loading i {
            font-size: 2.5rem;
            margin-bottom: 15px;
            display: block;
            color: var(--primary);
            animation: spin 2s linear infinite;
        }

        .message-actions {
            position: absolute;
            bottom: -15px;
            right: 20px;
            display: flex;
            gap: 10px;
            opacity: 0;
            transition: var(--transition);
        }

        .message:hover .message-actions {
            opacity: 1;
            transform: translateY(5px);
        }

        .action-btn {
            width: 30px;
            height: 30px;
            border-radius: 50%;
            background: rgba(15, 23, 42, 0.8);
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            transition: var(--transition);
            border: 1px solid var(--glass-border);
            color: var(--text-light);
        }

        .action-btn:hover {
            transform: scale(1.1);
            background: var(--primary);
        }

        /* 日志切换按钮 */
        .log-toggle {
            position: fixed;
            bottom: 20px;
            right: 20px;
            z-index: 100;
            width: 50px;
            height: 50px;
            border-radius: 50%;
            background: linear-gradient(135deg, var(--primary), var(--primary-dark));
            display: flex;
            align-items: center;
            justify-content: center;
            color: white;
            font-size: 1.2rem;
            cursor: pointer;
            box-shadow: 0 5px 15px rgba(74, 108, 247, 0.5);
            transition: var(--transition);
        }

        .log-toggle:hover {
            transform: translateY(-5px);
            box-shadow: 0 8px 20px rgba(74, 108, 247, 0.7);
        }

        /* 日志容器 - 默认隐藏 */
        .log-container {
            max-width: 1200px;
            margin: 20px auto;
            padding: 20px;
            position: relative;
            z-index: 5;
            background: rgba(10, 15, 30, 0.6);
            backdrop-filter: blur(10px);
            border-radius: 20px;
            border: 1px solid rgba(74, 108, 247, 0.2);
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
            height: 0;
            overflow: hidden;
            opacity: 0;
            transition: all 0.5s ease;
        }

        .log-container.visible {
            height: 200px;
            opacity: 1;
            margin-bottom: 20px;
        }

        .log-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 15px;
            padding-bottom: 10px;
            border-bottom: 1px solid rgba(74, 108, 247, 0.2);
        }

        .log-header h3 {
            font-size: 1.3rem;
            background: linear-gradient(90deg, var(--primary), var(--accent));
            -webkit-background-clip: text;
            background-clip: text;
            -webkit-text-fill-color: transparent;
        }

        .log-content {
            height: 130px;
            overflow-y: auto;
            padding-right: 10px;
        }

        .log-content::-webkit-scrollbar {
            width: 6px;
        }

        .log-content::-webkit-scrollbar-thumb {
            background: var(--primary);
            border-radius: 3px;
        }

        .log-entry {
            margin: 5px 0;
            padding: 8px 12px;
            border-radius: 10px;
            font-size: 0.9rem;
            background: rgba(15, 23, 42, 0.3);
            transition: var(--transition);
        }

        .log-entry:hover {
            background: rgba(30, 41, 59, 0.5);
        }

        .log-entry.system {
            border-left: 3px solid var(--text-muted);
        }

        .log-entry.recorder {
            border-left: 3px solid var(--primary);
        }

        .log-entry.player {
            border-left: 3px solid var(--success);
        }

        .log-entry.error {
            border-left: 3px solid var(--accent);
        }

        /* 动画效果 */
        @keyframes fadeUp {
            to {
                opacity: 1;
                transform: translateY(0);
            }
        }

        @keyframes spin {
            from { transform: rotate(0deg); }
            to { transform: rotate(360deg); }
        }

        @keyframes pulse {
            0%, 100% { opacity: 1; }
            50% { opacity: 0.3; }
        }

        @keyframes float {
            0%, 100% { transform: translateY(0); }
            50% { transform: translateY(-10px); }
        }

        .floating {
            animation: float 4s ease-in-out infinite;
        }

        /* 响应式设计 */
        @media (max-width: 768px) {
            header h1 {
                font-size: 1.8rem;
            }

            .control-panel, #dialogue-container, .log-container {
                margin: 15px;
                padding: 15px;
            }

            .control-buttons {
                flex-direction: column;
            }

            .control-btn {
                min-width: 100%;
            }

            #dialogue-container {
                height: calc(100vh - 450px);
            }

            .message {
                max-width: 90%;
                padding: 15px;
            }

            .message-content {
                font-size: 1rem;
            }
        }
    </style>
</head>
<body>
    <div class="tech-bg">
        <div class="grid-line"></div>
    </div>

    <header>
<!--        <h1><i class="fas fa-microphone-alt"></i> 百聆(BaiLing)</h1>-->
<!--        <div class="subtitle">智能语音对话系统</div>-->
        <h1><i class="fas fa-comments"></i> 百聆(BaiLing) <span style="font-size: 1.2rem; vertical-align: middle;">· 实时对话</span></h1>

    </header>

    <!-- 控制面板 -->
    <div class="control-panel">
        <div class="status-box">
            <div class="status-indicator disconnected"></div>
            <div class="status-text">状态: 未连接</div>
        </div>

        <div class="control-buttons">
            <button id="startBtn" class="control-btn">
                <i class="fas fa-play-circle"></i> 开始对话
            </button>
            <button id="stopBtn" class="control-btn stop" disabled>
                <i class="fas fa-stop-circle"></i> 停止
            </button>
            <button id="interruptBtn" class="control-btn interrupt" disabled>
                <i class="fas fa-hand-paper"></i> 打断
            </button>
        </div>

    </div>

    <!-- 对话容器 -->
    <div id="dialogue-container">
<!--        <div class="message role-system fade-in" style="opacity:1; transform:none;">-->
<!--            <div class="role-tag">系统</div>-->
<!--            <div class="message-content">-->
<!--                欢迎使用百聆智能语音对话系统！点击"开始对话"按钮开始体验-->
<!--            </div>-->
<!--            <div class="timestamp">10:00</div>-->
<!--        </div>-->

<!--        <div class="message role-bot fade-in" style="opacity:1; transform:none;">-->
<!--            <div class="role-tag">百聆</div>-->
<!--            <div class="message-content">-->
<!--                您好！我是百聆AI助手，可以为您提供自然流畅的语音对话服务-->
<!--            </div>-->
<!--            <div class="timestamp">10:00</div>-->
<!--        </div>-->
    </div>

    <!-- 日志容器 - 默认隐藏 -->
    <div class="log-container" id="logContainer">
        <div class="log-header">
            <h3><i class="fas fa-clipboard-list"></i> 系统日志</h3>
        </div>
        <div class="log-content" id="logContent">
            <div class="log-entry system">系统已就绪，等待用户操作</div>
        </div>
    </div>

    <!-- 日志切换按钮 -->
    <div class="log-toggle" id="logToggle">
        <i class="fas fa-bars"></i>
    </div>

    <script>
        function genId() {
          const arr = crypto.getRandomValues(new Uint32Array(1));
          return arr[0].toString(16); // 例如："9af3b2c"
        }
        function getUserId() {
          let uid = sessionStorage.getItem("user_id");
          if (!uid) {
            uid = genId();
            sessionStorage.setItem("user_id", uid);
          }
          return uid;
        }

        const userId = getUserId();
        console.log("Your user_id is", userId);
        // DOM元素
        const startBtn = document.getElementById('startBtn');
        const stopBtn = document.getElementById('stopBtn');
        const interruptBtn = document.getElementById('interruptBtn');
        const logToggle = document.getElementById('logToggle');
        const statusIndicator = document.querySelector('.status-indicator');
        const statusText = document.querySelector('.status-text');
        const logContainer = document.getElementById('logContainer');
        const logContent = document.getElementById('logContent');

        // 状态管理
        let websocket = null;
        let player = null;
        let recorder = null;
        let connectionStatus = 'disconnected';
        let logsVisible = false;

        // 更新状态显示
        function updateStatus(status, message) {
            connectionStatus = status;
            statusIndicator.className = 'status-indicator ' + status;
            statusText.textContent = '状态: ' + message;
        }

        // 添加日志
        function addLog(message, type = 'system') {
            const entry = document.createElement('div');
            entry.className = `log-entry ${type}`;
            entry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
            logContent.appendChild(entry);
            logContent.scrollTop = logContainer.scrollHeight;
        }


        // 切换日志显示
        function toggleLogs() {
            logsVisible = !logsVisible;
            logContainer.classList.toggle('visible', logsVisible);
            logToggle.innerHTML = logsVisible ? '<i class="fas fa-times"></i>' : '<i class="fas fa-bars"></i>';
        }

        // 更新对话
        function updateDialogue(dialogue) {
            const container = document.getElementById('dialogue-container');
            const existingMessages = container.querySelectorAll('.message');
            const existingCount = 0; //existingMessages.length;

            // 只添加新消息
            for (let i = existingCount; i < dialogue.length; i++) {
                const message = dialogue[i];
                const messageDiv = document.createElement('div');
                messageDiv.className = `message role-${message.role}`;

                // 获取当前时间
                const now = new Date();
                const timestamp = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`;

                // 角色标签文本
                const roleText = {
                    'user': '用户',
                    'assistant': '百聆',
                    'admin': '管理员',
                    'system': '系统'
                }[message.role] || message.role;

                messageDiv.innerHTML = `
                    <div class="role-tag">${roleText}</div>
                    <div class="message-content">
                        ${message.content}
                    </div>
                    <div class="timestamp">${timestamp}</div>
                    <div class="message-actions">
                        <div class="action-btn"><i class="fas fa-reply"></i></div>
                        <div class="action-btn"><i class="fas fa-share-alt"></i></div>
                    </div>
                `;

                container.appendChild(messageDiv);

                // 滚动到底部
                setTimeout(() => {
                    container.scrollTop = container.scrollHeight;
                }, 100);
            }
        }

        class AudioPlayer {
            constructor(updateStatusFn, websocket) {
                this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
                this.audioQueue = [];
                this.isPlaying = false;
                this.shouldInterrupt = false;
                this.log = document.getElementById('logContainer');
                this.updateStatus = updateStatusFn;
                this.websocket = websocket
            }

            addLog(message) {
                const entry = document.createElement('div');
                entry.className = 'log-entry';
                entry.textContent = `[播放器] ${new Date().toLocaleTimeString()}: ${message}`;
                this.log.appendChild(entry);
                this.log.scrollTop = this.log.scrollHeight;
            }

             sendPlaybackStatus(status, queueSize) {
                if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
                    const message = {
                        type: "playback_status",
                        status: status, // e.g., 'playing', 'stopped', 'interrupted'
                        queue_size: queueSize,
                        timestamp: new Date().toISOString()
                    };
                    this.websocket.send(JSON.stringify(message));
                }
            }

            async playAudio(audioData) {
                try {
                    const audioBuffer = await this.audioContext.decodeAudioData(audioData);
                    this.audioQueue.push(audioBuffer);
                    this.addLog(`收到音频, 队列大小: ${this.audioQueue.length}`);
                    if (!this.isPlaying) {
                        this.playNext();
                    }
                } catch (error) {
                    this.addLog(`播放错误: ${error}`);
                }
            }

            playNext() {
                if (this.audioQueue.length === 0) {
                    this.isPlaying = false;
                    return;
                }

                this.isPlaying = true;
                const audioBuffer = this.audioQueue.shift();
                const source = this.audioContext.createBufferSource();
                source.buffer = audioBuffer;
                source.connect(this.audioContext.destination);
                this.source = source;  // <== 添加这句


                const duration = audioBuffer.duration.toFixed(1);
                this.addLog(`开始播放 (时长: ${duration}秒)`);
                this.updateStatus(`播放中 (剩余: ${this.audioQueue.length + 1}段)`, 'playing');
                this.sendPlaybackStatus('playing', this.audioQueue.length);
                source.onended = () => {
                    this.addLog("播放完成");
                    if (this.shouldInterrupt){
                        this.sendPlaybackStatus('interrupted', this.audioQueue.length);
                    } else {
                        this.sendPlaybackStatus('completed', this.audioQueue.length);
                    }
                    if (this.audioQueue.length > 0) {
                        this.playNext();
                    } else {
                        this.isPlaying = false;
                        this.shouldInterrupt = false;

                        if (this.audioQueue.length === 0) {
                            this.updateStatus("就绪");
                        }
                    }
                };

                source.start();
            }

            stopCurrent() {
                if (this.source) {
                    try {
                        this.source.stop();
                    } catch (e) {
                        this.addLog("停止当前音频失败: " + e);
                    }
                    this.source = null;
                }
                this.isPlaying = false;
            }

            interrupt() {
                this.shouldInterrupt = true;
                this.audioQueue = [];
                this.stopCurrent();  // <== 添加这一句
                this.addLog("收到打断指令，清空播放队列并停止当前音频");
                this.updateStatus("已打断", "disconnected");
            }
        }

        // 改写的 WebRecorder，发送原始 PCM
        class WebRecorder {
            constructor(websocket) {
                this.websocket = websocket;
                this.audioContext = null;
                this.processor = null;
                this.source = null;
                this.stream = null;
                this.isRecording = false;
                this.log = document.getElementById('logContainer');

                // 配置参数
                this.inputSampleRate = 48000;  // 大多数浏览器默认采样率
                this.outputSampleRate = 16000; // 你需要的采样率
            }

            addLog(message) {
                const entry = document.createElement('div');
                entry.className = 'log-entry';
                entry.textContent = `[录音器] ${new Date().toLocaleTimeString()}: ${message}`;
                this.log.appendChild(entry);
                this.log.scrollTop = this.log.scrollHeight;
            }

            // 采样率转换（降采样）
            downsampleBuffer(buffer, inputSampleRate, outputSampleRate) {
                if (outputSampleRate === inputSampleRate) {
                    return buffer;
                }
                const sampleRateRatio = inputSampleRate / outputSampleRate;
                const newLength = Math.round(buffer.length / sampleRateRatio);
                const result = new Float32Array(newLength);
                let offsetResult = 0;
                let offsetBuffer = 0;
                while (offsetResult < result.length) {
                    const nextOffsetBuffer = Math.round((offsetResult + 1) * sampleRateRatio);
                    // 对应多个输入样本的平均值
                    let accum = 0, count = 0;
                    for (let i = offsetBuffer; i < nextOffsetBuffer && i < buffer.length; i++) {
                        accum += buffer[i];
                        count++;
                    }
                    result[offsetResult] = accum / count;
                    offsetResult++;
                    offsetBuffer = nextOffsetBuffer;
                }
                return result;
            }

            // float32 转 int16
            floatTo16BitPCM(input) {
                const output = new Int16Array(input.length);
                for (let i = 0; i < input.length; i++) {
                    let s = Math.max(-1, Math.min(1, input[i]));
                    output[i] = s < 0 ? s * 0x8000 : s * 0x7FFF;
                }
                return output;
            }

            async start() {
                try {
                    this.stream = await navigator.mediaDevices.getUserMedia({ audio: true });
                    this.audioContext = new (window.AudioContext || window.webkitAudioContext)();

                    // 连接麦克风流
                    this.source = this.audioContext.createMediaStreamSource(this.stream);

                    // 处理节点，bufferSize 可以是 256、512、1024、2048、4096、8192、16384
                    const bufferSize = 4096;
                    this.processor = this.audioContext.createScriptProcessor(bufferSize, 1, 1);

                    this.processor.onaudioprocess = (e) => {
                        if (!this.isRecording) return;

                        let inputData = e.inputBuffer.getChannelData(0); // Float32Array

                        // 先降采样到16kHz
                        let downsampledBuffer = this.downsampleBuffer(inputData, this.audioContext.sampleRate, this.outputSampleRate);

                        // 转16bit PCM
                        let int16Buffer = this.floatTo16BitPCM(downsampledBuffer);

                        // 发送二进制数据
                        if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
                            this.websocket.send(int16Buffer.buffer);
                            // this.addLog(`发送PCM数据: ${int16Buffer.byteLength}字节`);
                        }
                    };

                    // 连接处理节点到输出（避免垃圾收集）
                    this.source.connect(this.processor);
                    this.processor.connect(this.audioContext.destination);

                    this.isRecording = true;
                    this.addLog("录音已启动（PCM采样）");
                    return true;
                } catch (error) {
                    this.addLog(`录音启动失败: ${error}`);
                    return false;
                }
            }

            stop() {
                if (!this.isRecording) return;

                this.isRecording = false;
                this.addLog("录音已停止");

                if (this.processor) {
                    this.processor.disconnect();
                    this.processor = null;
                }
                if (this.source) {
                    this.source.disconnect();
                    this.source = null;
                }
                if (this.audioContext) {
                    this.audioContext.close();
                    this.audioContext = null;
                }
                if (this.stream) {
                    this.stream.getTracks().forEach(track => track.stop());
                    this.stream = null;
                }
            }
        }


        startBtn.addEventListener('click', async () => {
            updateStatus("连接中...");
            addLog("尝试连接到服务器...");

            try {
                const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
                websocket = new WebSocket(`${protocol}//${window.location.host}/ws?user_id=${userId}`);

                websocket.binaryType = 'arraybuffer';

                websocket.onopen = async () => {
                    updateStatus("已连接，准备录音...", "connected");
                    addLog("WebSocket连接已建立");

                    player = new AudioPlayer(updateStatus, websocket);
                    recorder = new WebRecorder(websocket);

                    const started = await recorder.start();
                    if (started) {
                        updateStatus("录音中...", "recording");
                        startBtn.disabled = true;
                        stopBtn.disabled = false;
                        interruptBtn.disabled = false;
                    }
                };

                websocket.onmessage = async (event) => {
                    // addLog("收到对话数据"+event.data);
                    if (typeof event.data === 'string') {
                        // addLog("收到对话数据"+event.data);
                        const message = JSON.parse(event.data);
                        if (message.type === 'interrupt') {
                            player.interrupt();
                            updateStatus("收到打断指令", "disconnected");
                            addLog("收到服务器打断指令"+event.data);
                        } else if (message.type == 'update_dialogue') {
                            addLog("收到对话数据"+event.data);
                            updateDialogue(message.data)
                        } else {
                            addLog("收到未知指令数据："+event.data);
                        }
                    } else {
                        const audioData = event.data; // ArrayBuffer
                        player.playAudio(audioData);
                    }
                };

                websocket.onerror = (error) => {
                    addLog(`WebSocket错误: ${error}`);
                    updateStatus("连接错误", "disconnected");
                };

                websocket.onclose = () => {
                    addLog("连接已关闭");
                    updateStatus("连接已关闭", "disconnected");
                    if (recorder) recorder.stop();
                    startBtn.disabled = false;
                    stopBtn.disabled = true;
                    interruptBtn.disabled = true;
                };

            } catch (error) {
                addLog(`连接失败: ${error}`);
                updateStatus("连接失败", "disconnected");
            }
        });

        stopBtn.addEventListener('click', () => {
            if (websocket) websocket.close();
            if (recorder) recorder.stop();
            updateStatus("已停止", "disconnected");
            startBtn.disabled = false;
            stopBtn.disabled = true;
            interruptBtn.disabled = true;
            addLog("对话已停止");
        });

        interruptBtn.addEventListener('click', () => {
            if (player) {
                player.interrupt();
                addLog("手动触发打断");
            }
        });

        // 日志切换按钮事件
        logToggle.addEventListener('click', toggleLogs);

        // 初始化日志
        addLog("系统初始化完成");
        addLog("日志面板默认隐藏，点击右下角按钮显示", 'system');

        // 模拟初始对话数据
        setTimeout(() => {
            const initialDialogue = [
                { role: 'system', content: '欢迎使用百聆实时对话系统，请点击开始对话按钮进行对话' },
                { role: 'bot', content: '您好！我是百聆智能语音助手，有什么可以帮您的吗？' }
            ];
            updateDialogue(initialDialogue);
        }, 1500);

    </script>
</body>
</html>
